import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.values.XmlComplexContentImpl;

import br.gov.ans.padroes.tiss.schemas.CtDiagnosticosSecundarios;
import br.gov.ans.padroes.tiss.schemas.CtGuiaConsulta;
import br.gov.ans.padroes.tiss.schemas.CtInternacaoObstetrica;

import com.datamelt.datagenerator.RegularExpressionDataGenerator;

public class Gerador {
	static String ALL_POSSIBLE_CHARACTERS = RegularExpressionDataGenerator.POSSIBLE_VALUES_LOWER_ALPHA
			+ RegularExpressionDataGenerator.POSSIBLE_VALUES_NUMERIC
			+ RegularExpressionDataGenerator.POSSIBLE_VALUES_UPPER_ALPHA;

	public static void main(String[] args) throws XmlException,
			IntrospectionException, SecurityException, NoSuchMethodException,
			IllegalArgumentException, IllegalAccessException,
			InvocationTargetException, NoSuchFieldException, IOException,
			ClassNotFoundException {
		XmlObject a = getComplexType(CtGuiaConsulta.class);
		a.save(System.out, new XmlOptions().setSavePrettyPrint().setSaveOuter()
				.setSaveInner().setSaveNamespacesFirst());
		System.out.println("\n" + a.validate());
	}

	private static XmlObject getComplexType(Class interfaceClass)
			throws IllegalAccessException, InvocationTargetException,
			NoSuchMethodException, IntrospectionException,
			NoSuchFieldException, IOException, IllegalArgumentException,
			SecurityException, ClassNotFoundException {
		RegularExpressionDataGenerator generator = new RegularExpressionDataGenerator();
		Class clazzImpl = getImplementationClass(interfaceClass);
		Class factory = getFactoryClass(interfaceClass);
		XmlObject a = (XmlObject) factory.getMethod("newInstance",
				new Class[] {}).invoke(null, new Object[] {});
		PropertyDescriptor propertyDescriptor[] = Introspector.getBeanInfo(
				clazzImpl, XmlComplexContentImpl.class)
				.getPropertyDescriptors();
		for (int i = 0; i < propertyDescriptor.length; i++) {
			if (!propertyDescriptor[i].getReadMethod().getName().startsWith(
					"is")) {
				System.out.println(propertyDescriptor[i].getName()
						+ " - "
						+ propertyDescriptor[i].getPropertyType()
						+ " - "
						+ propertyDescriptor[i].getReadMethod().getName()
						+ " - "
						+ " - "
						+ propertyDescriptor[i].getReadMethod().getReturnType()
						+ " - "
						+ XmlObject.class
								.isAssignableFrom(propertyDescriptor[i]
										.getReadMethod().getReturnType()));
				// TODO Tratar tipos complexos
				if (XmlObject.class.isAssignableFrom(propertyDescriptor[i]
						.getReadMethod().getReturnType())) {
					System.out.println("TIPO COMPLEXO: "
							+ propertyDescriptor[i].getReadMethod()
									.getReturnType().getPackage()
							+ ".impl."
							+ propertyDescriptor[i].getReadMethod()
									.getReturnType().getSimpleName() + "Impl");
					interfaceClass.getMethod(
							"set"
									+ propertyDescriptor[i].getName()
											.substring(0, 1).toUpperCase()
									+ propertyDescriptor[i].getName()
											.substring(1),
							propertyDescriptor[i].getReadMethod()
									.getReturnType()).invoke(
							a,
							getComplexType(propertyDescriptor[i]
									.getReadMethod().getReturnType()));
					propertyDescriptor[i].getReadMethod().invoke(a, null);
				}
				// TODO Tratar Arrays.
				else if (propertyDescriptor[i].getReadMethod().getReturnType()
						.isArray()) {
					System.out.println("ARRAY");
					if (!XmlObject.class
							.isAssignableFrom(propertyDescriptor[i]
									.getReadMethod().getReturnType()
									.getComponentType())) {
						System.out.println("PRIMITIVO");
						System.out.println(interfaceClass.getMethod(
								"x"
										+ propertyDescriptor[i].getReadMethod()
												.getName(), new Class[] {}));
						System.out.println(interfaceClass.getMethod(
								"x"
										+ propertyDescriptor[i].getReadMethod()
												.getName(), new Class[] {})
								.getReturnType().getComponentType());
						SchemaType type = (SchemaType) interfaceClass
								.getMethod(
										"x"
												+ propertyDescriptor[i]
														.getReadMethod()
														.getName(),
										new Class[] {}).getReturnType()
								.getComponentType().getField("type").get(null);
						System.out.println(type);
					} else {
						System.out.println("COMPLEXO");
					}

				}
				// Tipos simples
				else {
					System.out.println("SIMPLES");
					Method simpleTypeRead = interfaceClass.getMethod("x"
							+ propertyDescriptor[i].getReadMethod().getName(),
							new Class[] {});
					Method simpleTypeWrite = interfaceClass.getMethod("x"
							+ propertyDescriptor[i].getWriteMethod().getName(),
							new Class[] { simpleTypeRead.getReturnType() });

					Field fType = simpleTypeRead.getReturnType().getField(
							"type");
					SchemaType sType = (SchemaType) fType.get(null);
					System.out.println(sType);
					System.out.println(sType.getBaseType());
					System.out.println(sType.getPrimitiveType());
					System.out.println(sType.isSimpleType());
					System.out.flush();
					if (sType.hasPatternFacet()) {
						String data = generator.generateData(sType
								.getPatterns()[(int) (Math.random() * sType
								.getPatterns().length)]);
						simpleTypeWrite.invoke(a, sType.newValue(data));
					} else if (sType.hasStringEnumValues()) {
						simpleTypeWrite
								.invoke(a, sType.newValue(sType
										.getStringEnumEntries()[(int) (Math
										.random() * sType
										.getStringEnumEntries().length)]
										.getString()));
					} else if (sType.isNumeric()) {
						int casasDecimais = (int) Math.log10((double) sType
								.getDecimalSize()) + 1;
						String data = generator.generateData("[0-9]{1,"
								+ casasDecimais + "}");
						simpleTypeWrite.invoke(a, sType.newValue(data));
					} else if (sType.getFacet(SchemaType.FACET_MAX_LENGTH) != null
							|| sType.getFacet(SchemaType.FACET_MAX_EXCLUSIVE) != null
							|| sType.getFacet(SchemaType.FACET_MAX_INCLUSIVE) != null
							|| sType.getFacet(SchemaType.FACET_MIN_LENGTH) != null
							|| sType.getFacet(SchemaType.FACET_MIN_EXCLUSIVE) != null
							|| sType.getFacet(SchemaType.FACET_MIN_INCLUSIVE) != null) {

						String min = getMin(sType);

						String max = getMax(sType);

						String data = generator.generateData("["
								+ ALL_POSSIBLE_CHARACTERS + "]{" + min + ","
								+ max + "}");
						simpleTypeWrite.invoke(a, sType.newValue(data));
					}
				}
			}
		}
		return a;
	}

	private static Class getFactoryClass(Class interfaceClass) {
		Class factory = null;
		for (int i = 0; i < interfaceClass.getDeclaredClasses().length; i++)
			if (interfaceClass.getDeclaredClasses()[i].getName().endsWith(
					"Factory"))
				factory = interfaceClass.getDeclaredClasses()[i];
		return factory;
	}

	private static String getMax(SchemaType sType) {
		if (sType.getFacet(SchemaType.FACET_MAX_INCLUSIVE) != null)
			return sType.getFacet(SchemaType.FACET_MAX_INCLUSIVE)
					.getStringValue();
		else if (sType.getFacet(SchemaType.FACET_MAX_EXCLUSIVE) != null) {
			Integer aux = Integer.valueOf(sType.getFacet(
					SchemaType.FACET_MAX_EXCLUSIVE).getStringValue());
			return String.valueOf(aux.intValue() - 1);
		} else if (sType.getFacet(SchemaType.FACET_MAX_LENGTH) != null)
			return sType.getFacet(SchemaType.FACET_MAX_LENGTH).getStringValue();
		return null;
	}

	private static String getMin(SchemaType sType) {
		if (sType.getFacet(SchemaType.FACET_MIN_INCLUSIVE) != null)
			return sType.getFacet(SchemaType.FACET_MIN_INCLUSIVE)
					.getStringValue();
		else if (sType.getFacet(SchemaType.FACET_MIN_EXCLUSIVE) != null) {
			Integer aux = Integer.valueOf(sType.getFacet(
					SchemaType.FACET_MIN_EXCLUSIVE).getStringValue());
			return String.valueOf(aux.intValue() + 1);
		} else if (sType.getFacet(SchemaType.FACET_MIN_LENGTH) != null)
			return sType.getFacet(SchemaType.FACET_MIN_LENGTH).getStringValue();
		return null;
	}

	public static Class getImplementationClass(Class interfaceClass)
			throws ClassNotFoundException {
		if (interfaceClass.isMemberClass())
			return Class.forName(interfaceClass.getDeclaringClass()
					.getPackage().getName()
					+ ".impl."
					+ interfaceClass.getDeclaringClass().getSimpleName()
					+ "Impl" + "$" + interfaceClass.getSimpleName() + "Impl");
		return Class.forName(interfaceClass.getPackage().getName() + ".impl."
				+ interfaceClass.getSimpleName() + "Impl");
	}
}
